package br.sed.theenemy.business.utils;

import org.apache.commons.lang.StringUtils;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ValidationUtil {
    
    private static final String REGEX_POSITIVE_NUMBER = "[0-9]+";
    private static final String REGEX_NEGATIVE_NUMBER = "\\-0*[1-9][0-9]*";
    
    /** CEP Ranges by UF */
    private static final int TO_77999 = 77999;
    private static final int TO_77000 = 77000;
    private static final int SP_19999 = 19999;
    private static final int SP_1000 = 1000;
    private static final int SE_49999 = 49999;
    private static final int SE_49000 = 49000;
    private static final String TO = "TO";
    private static final String SP = "SP";
    private static final String SE = "SE";
    private static final int SC_89999 = 89999;
    private static final int SC_88000 = 88000;
    private static final String SC = "SC";
    private static final int RS_99999 = 99999;
    private static final int RS_90000 = 90000;
    private static final String RS = "RS";
    private static final int RR_69399 = 69399;
    private static final int RR_69300 = 69300;
    private static final String RR = "RR";
    private static final int RO_76999 = 76999;
    private static final int RO_76800 = 76800;
    private static final String RO = "RO";
    private static final int RN_59999 = 59999;
    private static final int RN_59000 = 59000;
    private static final String RN = "RN";
    private static final int RJ_28999 = 28999;
    private static final int RJ_20000 = 20000;
    private static final int PR_87999 = 87999;
    private static final int PR_80000 = 80000;
    private static final String RJ = "RJ";
    private static final String PR = "PR";
    private static final int PI_64999 = 64999;
    private static final int PI_64000 = 64000;
    private static final String PI = "PI";
    private static final int PE_56999 = 56999;
    private static final int PE_50000 = 50000;
    private static final String PE = "PE";
    private static final int PB_58999 = 58999;
    private static final int PB_58000 = 58000;
    private static final String PB = "PB";
    private static final int PA_68899 = 68899;
    private static final int PA_66000 = 66000;
    private static final String PA = "PA";
    private static final int MT_78899 = 78899;
    private static final int MT_78000 = 78000;
    private static final String MT = "MT";
    private static final int MS_79999 = 79999;
    private static final int MS_79000 = 79000;
    private static final String MS = "MS";
    private static final int MG_39999 = 39999;
    private static final int MG_30000 = 30000;
    private static final String MG = "MG";
    private static final int MA_65999 = 65999;
    private static final int MA_65000 = 65000;
    private static final String MA = "MA";
    private static final int GO_76799 = 76799;
    private static final int GO_73700 = 73700;
    private static final int GO_72999 = 72999;
    private static final int GO_72800 = 72800;
    private static final String GO = "GO";
    private static final int ES_29999 = 29999;
    private static final int ES_29000 = 29000;
    private static final String ES = "ES";
    private static final int DF_73699 = 73699;
    private static final int DF_73000 = 73000;
    private static final int DF_72799 = 72799;
    private static final int DF_70000 = 70000;
    private static final String DF = "DF";
    private static final int CE_63999 = 63999;
    private static final int CE_60000 = 60000;
    private static final String CE = "CE";
    private static final int BA_48999 = 48999;
    private static final int BA_40000 = 40000;
    private static final String BA = "BA";
    private static final int AP_68999 = 68999;
    private static final int AP_68900 = 68900;
    private static final String AP = "AP";
    private static final int AM_69899 = 69899;
    private static final int AM_69400 = 69400;
    private static final int AM_69299 = 69299;
    private static final int AM_69000 = 69000;
    private static final String AM = "AM";
    private static final int AL_57999 = 57999;
    private static final int AL_57000 = 57000;
    private static final String AL = "AL";
    private static final String AC = "AC";
    private static final int AC_69999 = 69999;
    private static final int AC_69900 = 69900;
    
    private static final int PARTIAL_CEP_SIZE = 5;
    
    private static final int PASSWORD_SIZE = 8;

    private static final String NON_WORD_DIGIT = "[\\W|_]";
    private static final String WORD_DIGIT = "[a-zA-Z]";
    private static final String DIGIT_FILTER = "\\d";
    
    private static final String[] BANNED_SYMBOLS = new String[] { ";", "<", ">", "=", "\"" };
    
  //convert the string "[A-Za-z�-��-�]([`�'\\-]?[A-Za-z�-��-�]+)*" to unicode 
    private static final String REGEX_WORD = "[A-Za-z\u00C1-\u00DD\u00E1-\u00FF\\\\d\'\\\"&\\\\(\\\\)\\\\-`\u00B4\u00AA\u00BA\u00B0;:\\\\.,/]+( [A-Za-z\u00C1-\u00DD\u00E1-\u00FF\\\\d\'\\\"&\\\\(\\\\)\\\\-`\u00B4\u00AA\u00BA\u00B0;:\\\\.,/]+)*";
    
    public static final String REGEX_NAME = new StringBuffer("(").append(REGEX_WORD).append("\\.?)( ")
    .append(REGEX_WORD).append("\\.?)*").toString();
    
    public static final String REGEX_EMAIL = new StringBuffer("(?:[a-z0-9!#$%&\'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&\'*+/=?^_`{|}~")
    .append("-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c")
    .append("\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(")
    .append("?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]")
    .append(":(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])").toString();
    
    private static final String REGEX_CPF_FULL = "(\\d{3})\\.(\\d{3})\\.(\\d{3})\\-(\\d{2})";
    private static final String REGEX_CPF_ONLY_NUMS = "(\\d{3})(\\d{3})(\\d{3})(\\d{2})";
    
    private static final  int CPF_SIZE = 11;
    private static final int DEFAULT_WEIGHT = 10;
    
    public static final String REGEX_CEP = "[\\d]{5}\\-[\\d]{3}";
    
    public static enum CpfPattern {
        full (REGEX_CPF_FULL),
        onlyNumbers (REGEX_CPF_ONLY_NUMS);

        private final String regex;

        /**
         * CPF Pattern.
         * @param regex
         */
        private CpfPattern(final String regex) {
            this.regex = regex;
        }
    }
    
    /**
     * Validate CPF (check digits).
     * Pattern: nnnNNNnnnNN
     * @param cpf
     * @return boolean
     */
    public static boolean isCpfValid(final String cpf) {
        return isCpfValid(cpf, CpfPattern.onlyNumbers);
    }
    
    /**
     * Validates CPF from certain pattern.
     * @param cpf
     * @param format
     * @return boolean
     */
    public static boolean isCpfValid(final String cpf, final CpfPattern format) {
        if (!isNullOrEmpty(cpf)) {
            final Pattern pattern = Pattern.compile(format.regex);
            final Matcher matcher = pattern.matcher(cpf);

            // Checks whether the entry matches pattern.
            if (matcher.matches()) {
                // Returns only the 9 digits.
                final String number = new StringBuffer(matcher.group(1)).append(matcher.group(2))
                .append(matcher.group(3)).toString();
                // Returns the 2 last digits (verification).
                final String verifyDig = matcher.group(4);

                // Prevents CPFs as 000.000.000-00 or 888.888.888-88 are accepted.
                if (areSameAlgarisms(number)) {
                    return false;
                }
                return calcVerificationDigits(number).equals(verifyDig);
            }
        }
        return false;
    }
    
    /**
     * Verifies that all characters are the same.
     * @param str
     * @return boolean
     */
    private static boolean areSameAlgarisms(final String str) {
        final char first = str.charAt(0);
        final char []strChar = str.toCharArray(); 
        for (final char c : strChar) {
            if(c != first){
                return false;
            }
        }
        return true;
    }
    
    /**
     * Calculates the CPF verification digits.
     * @param number
     * @return 2 check digits
     */
    private static String calcVerificationDigits(final String number) {
        Integer firstDig, secondDig;
        int sum = 0, weight = DEFAULT_WEIGHT;
        for (int i = 0; i < number.length(); i++) {
            sum += Integer.parseInt(number.substring(i, i + 1)) * weight--;
        }

        if (sum % CPF_SIZE == 0 | sum  % CPF_SIZE == 1) {
            firstDig = 0;
        } else {
            firstDig = CPF_SIZE - (sum % CPF_SIZE);
        }   

        sum = 0;
        weight = CPF_SIZE;
        for (int i = 0; i < number.length(); i++) {
            sum += Integer.parseInt(number.substring(i, i + 1)) * weight--;
        }

        sum += firstDig.intValue() * 2;
        if (sum % CPF_SIZE == 0 | sum % CPF_SIZE == 1) {
            secondDig = 0;
        } else {
            secondDig = CPF_SIZE - (sum % CPF_SIZE);
        }   

        return new StringBuffer(firstDig.toString()).append(secondDig.toString()).toString();
    }
    
    /**
     * Checks whether the CEP is valid.
     * Pattern: nnnnn-nnn
     * @param cep
     * @return boolean
     */
    public static boolean isCepValid(final String cep) {
        if (cep == null) {
            return false;
        }
        return cep.matches(REGEX_CEP);
    }
    
    /**
     * Checks whether the parameter is null or empty.
     * 
     * Examples:
     *     isNullOrEmpty(null) - returns true
     *     isNullOrEmpty("") - returns true
     *     isNullOrEmpty("   ") - returns true
     *     isNullOrEmpty("foo") - returns false
     *     isNullOrEmpty(" bar ") - returns false
     *     
     * @param s 
     * @return boolean
     */
    public static boolean isNullOrEmpty(final String s){
        if (s == null){
            return true;
        }
        
        if ("".equals(s.trim())) {
            return true;
        }
        
        return false;
    }
    
    /**
     * Checks whether an email address is valid.
     * @param email 
     * @return boolean
     */
    public static boolean isEmailValid(final String email){
        boolean result;

        // Ckecks whether is null or valid.
        if(isNullOrEmpty(email)){
            return false;
        }

        // Checks whether is valid.
        result = Pattern.matches(REGEX_EMAIL, email);
        return result;
    }
    
    /**
     * Validates whether the received password is weak.
     * For this case, a password is weak if not meet any one of spots:
     * <ul>
     * <li>Have at least 8 characters</li>
     * <li>Have at least one letter (a-zA-Z)</li>
     * <li>Have at least one digit (0-9)</li>
     * <li>Have at least one special character (\\W or "_")</li>
     * </ul>
     * @param password 
     * @return boolean
     */
    public static boolean isPasswordEasy2(final String password) {
        /* Checks size greater than 8 characters. */
        if(password.length() < PASSWORD_SIZE) {
            return true;
        }

        // Verifies that has at least one number (0-9).
        final String digitFilter = DIGIT_FILTER;
        Pattern patern = Pattern.compile( digitFilter );
        if(!patern.matcher( password ).find()) {
            return true;
        }

        // Verifies that has at least one "normal" character (a-zA-Z).
        final String wordDigit = WORD_DIGIT;
        patern = Pattern.compile( wordDigit );
        if(!patern.matcher( password ).find()) {
            return true;
        }

        
        //Verifies that has at least one special character
        //("_" is considered special character)
        final String nonWordDigit = NON_WORD_DIGIT;
        patern = Pattern.compile( nonWordDigit );
        if( !patern.matcher( password ).find() ) {
            return true;
        }

        return false;
    }
    
    /**
     * Checks whether the word contains whitespace.
     * @param str
     * @return
     */
    public static String trimWords(final String str) {
        if (str == null) {
            return null;
        }
        return str.trim().replaceAll("  +", " ");
    }
    
    /**
     * Checks whether the CEP is valid according to its State.<br>
     * Pattern: nnnnn-nnn
     * @param cep
     * @return boolean
     */
    public static boolean isCepAndStateValid(final String cep,final String uf) {
        int cep1 = 0;
        if (cep == null || uf == null) {
            return false;
        } else {
            cep1 = Integer.parseInt(cep.substring(0, PARTIAL_CEP_SIZE));
        }

        if (uf.equals(AC)) {
            if ((cep1 >= AC_69900) && (cep1 <= AC_69999)){
                return true;
            }       
        } else if (uf.equals(AL)) {
            if ((cep1 >= AL_57000) && (cep1 <= AL_57999)){
                return true;
            }       
        } else if (uf.equals(AM)) {
            if ((cep1 >= AM_69000) && (cep1 <= AM_69299)) {
                return true;
            } else if ((cep1 >= AM_69400) && (cep1 <= AM_69899)) {
                return true;
            }
        } else if (uf.equals(AP)) {
            if ((cep1 >= AP_68900) && (cep1 <= AP_68999)){
                return true;
            }       
        } else if (uf.equals(BA)) {
            if ((cep1 >= BA_40000) && (cep1 <= BA_48999)){
                return true;
            }       
        } else if (uf.equals(CE)) {
            if ((cep1 >= CE_60000) && (cep1 <= CE_63999)){
                return true;
            }       
        } else if (uf.equals(DF)) {
            if ((cep1 >= DF_70000) && (cep1 <= DF_72799)) {
                return true;
            } else if ((cep1 >= DF_73000) && (cep1 <= DF_73699)) {
                return true;
            }
        } else if (uf.equals(ES)) {
            if ((cep1 >= ES_29000) && (cep1 <= ES_29999)){
                return true;
            }       
        } else if (uf.equals(GO)) {
            if  ((cep1 >= GO_72800) && (cep1 <= GO_72999)){
                return true;
            } else if ((cep1 >= GO_73700) && (cep1 <= GO_76799)) {
                return true;
            }
        } else if (uf.equals(MA)) {
            if ((cep1 >= MA_65000) && (cep1 <= MA_65999)){
                return true;
            }       
        } else if (uf.equals(MG)) {
            if ((cep1 >= MG_30000) && (cep1 <= MG_39999)){
                return true;
            }       
        } else if (uf.equals(MS)) {
            if ((cep1 >= MS_79000) && (cep1 <= MS_79999)){
                return true;
            }       
        } else if (uf.equals(MT)) {
            if ((cep1 >= MT_78000) && (cep1 <= MT_78899)){
                return true;
            }       
        } else if (uf.equals(PA)) {
            if ((cep1 >= PA_66000) && (cep1 <= PA_68899)){
                return true;
            }       
        } else if (uf.equals(PB)) {
            if ((cep1 >= PB_58000) && (cep1 <= PB_58999)){
                return true;
            }       
        } else if (uf.equals(PE)) {
            if ((cep1 >= PE_50000) && (cep1 <= PE_56999)){
                return true;
            }       
        } else if (uf.equals(PI)) {
            if ((cep1 >= PI_64000) && (cep1 <= PI_64999)){
                return true;
            }       
        } else if (uf.equals(PR)) {
            if ((cep1 >= PR_80000) && (cep1 <= PR_87999)){
                return true;
            }       
        } else if (uf.equals(RJ)) {
            if ((cep1 >= RJ_20000) && (cep1 <= RJ_28999)){
                return true;
            }       
        } else if (uf.equals(RN)) {
            if ((cep1 >= RN_59000) && (cep1 <= RN_59999)){
                return true;
            }       
        }  else if (uf.equals(RO)) {
            if ((cep1 >= RO_76800) && (cep1 <= RO_76999)){
                return true;
            }       
        }  else if (uf.equals(RR)) {
            if ((cep1 >= RR_69300) && (cep1 <= RR_69399)){
                return true;
            }       
        }  else if (uf.equals(RS)) {
            if ((cep1 >= RS_90000) && (cep1 <= RS_99999)){
                return true;
            }       
        }  else if (uf.equals(SC)) {
            if ((cep1 >= SC_88000) && (cep1 <= SC_89999)){
                return true;
            }       
        }  else if (uf.equals(SE)) {
            if ((cep1 >= SE_49000) && (cep1 <= SE_49999)){
                return true;
            }       
        }  else if (uf.equals(SP)) {
            if ((cep1 >= SP_1000) && (cep1 <= SP_19999)){
                return true;
            }       
        }  else if (uf.equals(TO)) {
            if ((cep1 >= TO_77000) && (cep1 <= TO_77999)){
                return true;
            }       
        } 

        return false;
    }  
    
    /**
     * Checks if the string contains invalid simbols ( ; < >  = " ). 
     * @param value 
     * @return True 
     */
    public static boolean contaisBannedSymbols( final String value ) {
        if ( !isNullOrEmpty( value ) ) {
            for( int i = 0 ; i < BANNED_SYMBOLS.length ; i++ ){
                if ( value.indexOf( BANNED_SYMBOLS[i] ) != -1 ){
                    return true;
                }
            }
        }

        return false;
    }
    
    /**
     * Checa se o par�metro � um inteiro.<br>
     * @param num num
     * @return a boolean
     */
    public static boolean isIntNumber(final String num){
        boolean result;
        
        if(isNullOrEmpty(num)){
            return false;
        }
        
        result = Pattern.matches(REGEX_POSITIVE_NUMBER, num.trim()) || Pattern.matches(REGEX_NEGATIVE_NUMBER, num.trim());
        
        return result;
    }
    
    /**
     * Validates Voter Registration number
     * @param num
     * @param format
     * @return boolean
     */
    public static boolean isVoterRegistrationValid(final String num) {
        
        // Check if it is empty
        if (isNullOrEmpty(num)) {
            return false;
        }
        
        // Check if it is numeric
        if (!isNumeric(num)) {
            return false;
        }
        
        String paddedNum = StringUtils.leftPad(num, 12, "0");


        // get parts of the voter registration number
        final String initialSequence = paddedNum.substring(0, paddedNum.length() - 4);
        final String areaCode = paddedNum.substring(paddedNum.length() - 4, paddedNum.length() - 2);
        final String verifyDigit = paddedNum.substring(paddedNum.length() - 2);

        // Check if the area code is valid
        if (!isAreaCodeValid(areaCode)) {
            return false;
        }

        // Calculate the verify digit one
        int total = 0;
        int[] multipliers = { 2,3,4,5,6,7,8,9 };
        for (int i = 0; i < initialSequence.length(); i++) {
            total += charToInt(initialSequence.charAt(i)) * multipliers[i];
        }
        
        Integer digitOne = getDigit(total, areaCode);
                
        total = 0;
        total = charToInt(areaCode.charAt(0)) * 7 + charToInt(areaCode.charAt(1)) * 8 + digitOne * 9;
        
        //Calculate the verify digit two
        Integer digitTwo = getDigit(total, areaCode);
        
        StringBuffer digits = new StringBuffer();
        digits.append(digitOne.toString());
        digits.append(digitTwo.toString());
        
          
        return verifyDigit.equals(digits.toString());
    }
    
    //Get the verify digits
    private static int getDigit(int total, String areaCode){
        
        int rest = total % 11;
        if (rest == 0 && (areaCode.equals("01") || areaCode.equals("02")) ){
            rest = 1;
        }
        else if(rest == 10){
            rest = 0;
        }
        return rest;
    }
    
    //Turns a character in number
    private static int charToInt(char c) {
        return c - 48;
    }
    
    //Verify if the string is numeric
    private static boolean isNumeric(String s) {
        return Pattern.matches("\\d+", s);
    }
    
    //Verify if the area code is valid
    private static boolean isAreaCodeValid(String areaCode) {
        if ((areaCode.charAt(0) < '0') || (areaCode.charAt(0) > '2')) {
            return false;
        } else if ((areaCode.charAt(0) == '0')
                && (areaCode.charAt(1) == '0')) {
            return false;
        } else if ((areaCode.charAt(0) == '2')
                && (areaCode.charAt(1) == '9')) {
            return false;
        }
        return true;
    }
    
}
